package moteur;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.Math;

import tools.FrenchStemmer;
import tools.Normalizer;

import biblioteque.Bibliotheque;

/**
 * Classe Moteur
 * On effectuera les operations de recherche dans cette classe
 * @author Thelo
 *
 */
public class Moteur {

	HashMap<String, Document> docPertinents;
	private Bibliotheque bibli;
	
	public static float ANGLELIMIT = 1.5f;
	public static int NBDOC = 10;
	
	/**
	 * Constructeur initialise la map des documents pertinents et la bibliotheque
	 * @param bib La bibliotheque ˆ garder en mŽmoire
	 */
	public Moteur(Bibliotheque bib){
		docPertinents = new HashMap<String, Document>();
		this.bibli = bib;
	}
	
	/**
	 * Effectue une recherche de la requete suivant le modele vectoriel 
	 * @param requete La chaine passée en parametre par l'utilisateur
	 * @return Liste de document triés suivant la pertinence
	 * @throws IOException 
	 */
	public List<Entry<String, Document>> rechercheModeleVectoriel(String requete) {
		requete = requete.toLowerCase();
		HashMap<String, Integer> docsPourUnMot = new HashMap<String, Integer>();
		Normalizer tokenizer = new FrenchStemmer();
		String requeteTmp = "", motTmp = "";
		ArrayList<String> list = new ArrayList<String>();
		
		//DŽcoupage de la requete
		String[] maRequete = requete.split(" ");
		for (int i = 0 ; i < maRequete.length ; ++i){
			//On enleve l'eventuel "+" en dŽbut de mot
			if(maRequete[i].matches("\\+.*")){
				motTmp = maRequete[i].substring(1);
				requeteTmp += motTmp;
			}
			else
				requeteTmp += maRequete[i];
			requeteTmp += " ";
		}
		requeteTmp = requeteTmp.substring(0,requeteTmp.length()-1);
		
		try {
			list = tokenizer.normalizeRequete(requeteTmp);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		
		System.out.println(requeteTmp + " " + list.size() + " " + list);
		
		//On dŽtermine l'ensmeble des documents pertinents pour cette requete
		for(int i = 0 ; i < maRequete.length ; ++i){
			if(!maRequete[i].matches("\\+.*")){
				maRequete[i] = list.get(i);
				if (bibli.existeMotStemm(maRequete[i])){
					docsPourUnMot = bibli.getDocsPourUnMotStemm(maRequete[i]);
					//System.out.println("taille 1 : " + docsPourUnMot.size());
					for(Entry<String, Integer> entry : docsPourUnMot.entrySet()) {
						Document doc = new Document(entry.getValue());
					    docPertinents.put(entry.getKey(), doc);
					}
				}
			}
			else{
				String monMotSansPlus = maRequete[i].substring(1);
				if (bibli.existeMotSansNorm(monMotSansPlus)){
					docsPourUnMot = bibli.getDocsPourUnMotSansNorm(monMotSansPlus);
					for(Entry<String, Integer> entry : docsPourUnMot.entrySet()) {
						Document doc = new Document(entry.getValue());
					    docPertinents.put(entry.getKey(), doc);
					}
				}
			}
		}
System.out.println("taille 2 : " + docPertinents.size());
		//Pour chaque fichier pertinent, on calcule la similaritŽ cosinus
		for(Entry<String, Document> entry2 : docPertinents.entrySet()) {
			float sim = calculSimilariteCos(maRequete, (entry2.getValue()).getIdDocument(),entry2.getKey());
			Document doc = entry2.getValue();
			doc.setResult(sim);
			docPertinents.put(entry2.getKey(), doc);
		}
		
		System.out.println("taille 3 : " + docPertinents.size());
		
		//Tri du rŽsultat selon le rŽsultat 
		List<Entry<String, Document>> listeTri = new ArrayList<Entry<String, Document>>(docPertinents.entrySet());
		Collections.sort(listeTri, new ComparateurDocument());
		
		System.out.println("taille 4 : " + listeTri.size());
		
		//RAZ de docPertinents
		docPertinents = new HashMap<String, Document>();
		
		//Pertinence : on Žvite les documents trop ŽloignŽs de la requete		
		List<Entry<String, Document>> renvoi = new ArrayList<Entry<String, Document>>();
		
		for(Entry<String, Document> entry : listeTri) {
			if (entry.getValue().getResult() < ANGLELIMIT){
				System.out.println("Angle " + entry.getValue().getResult() +"<"+ ANGLELIMIT);
				renvoi.add(entry);
			}
		}
		System.out.println(renvoi.size());
		//Si aucune valeur correspondant aux criteres de pertinence n'a ŽtŽ trouvŽe, alors on affiche les NBDOC premiers documents.
		if (renvoi.isEmpty()){
			for(int i = 0 ; i < NBDOC && i < listeTri.size() ; ++i) {
				renvoi.add(listeTri.get(i));
			}
		}
		
		System.out.println("taille 5 : " + renvoi.size());
		
		return renvoi;
	}
	
	/**
	 * Calcul la similarite consinus entre un document et la requete 
	 * @param requete La chaine passée en parametre par l'utilisateur
	 * @param id L'identifiant du document ˆ Žtudier
	 * @param nomDoc Le nom du document ˆ Žtudier
	 * @return l'angle rŽsultant du calcul
	 */
	private float calculSimilariteCos(String[] maRequete, int id, String nomDoc){
		float sim = 0.0f, num = 0.0f, den = 0.0f, denReq = 0.0f, denDoc = 0.0f, df = 0.0f, tf = 0.0f, poidsDoc = 0.0f;
		String monMotSansPlus;
		Document doc = docPertinents.get(nomDoc);
		ArrayList<String> motsPresentsDansDoc = new ArrayList<String>();
		ArrayList<Float> poidsRequete = new ArrayList<Float>();
		
		//Calcul du poids des mots de la requete
		for(int i = 0 ; i < maRequete.length ; ++i, df = 0.0f, tf = 0.0f){
			if(!maRequete[i].matches("\\+.*")){
				if (!bibli.existeMotStemm(maRequete[i])) poidsRequete.add(i,0.0f);
				else{
					df = (float) Math.log10(bibli.getNbTotalDocs() / bibli.getNbDocPourMotStemm(maRequete[i]) + 1.0f);
					//System.out.println("df :" + df);
					tf = 1.0f;
					poidsRequete.add(i,df*tf);
				}
			}
			else{
				monMotSansPlus = maRequete[i].substring(1);
				if (!bibli.existeMotSansNorm(monMotSansPlus)) poidsRequete.add(i,0.0f);
				else{
					df =(float) Math.log10(bibli.getNbTotalDocs() / bibli.getNbDocPourMotSansNorm(monMotSansPlus) + 1.0f);
					tf = 1.0f;
					poidsRequete.add(i,df*tf);
				}
			}
		}
		
		//Calcul du numerateur
		for(int i = 0 ; i < maRequete.length ; ++i, df = 0.0f, tf = 0.0f, poidsDoc = 0.0f){
			if(!maRequete[i].matches("\\+.*")){
				if(bibli.existeMotStemmDansDoc(maRequete[i], id)){
					motsPresentsDansDoc.add(maRequete[i]);
					poidsDoc = bibli.getOccMotStemmDansDoc(maRequete[i], id) * ((float) Math.log10(bibli.getNbTotalDocs() / bibli.getNbDocPourMotStemm(maRequete[i])));
					System.out.println("poids doc : " + bibli.getOccMotStemmDansDoc(maRequete[i], id));
				}
			}
			else{
				monMotSansPlus = maRequete[i].substring(1);
				if (bibli.existeMotSansNormDansDoc(monMotSansPlus,id)){
					motsPresentsDansDoc.add(monMotSansPlus);
					poidsDoc = bibli.getOccMotSansNormDansDoc(monMotSansPlus, id) * ((float) Math.log10(bibli.getNbTotalDocs() / bibli.getNbDocPourMotSansNorm(monMotSansPlus)));
					
				}
			}
			System.out.println("poids du mot : " + poidsDoc); 
			num += poidsRequete.get(i) * poidsDoc;
			denReq += poidsRequete.get(i) * poidsRequete.get(i);
			denDoc += poidsDoc * poidsDoc;
		}

		
		System.out.println("Formule : " + num + " / rac(" + Math.sqrt(denReq) + ")*( " + Math.sqrt(denDoc) + " ) ");

		System.out.println("denReq : " + denReq + " denDoc : "+ denDoc);

		den = (float) (Math.sqrt(denReq) * Math.sqrt(denDoc));
		
		//On sauvegarde la liste des mots prŽsents dans ce document
		doc.setMots(motsPresentsDansDoc);
		docPertinents.put(nomDoc, doc);
		
		//Calcul final de la similaritŽ
		sim = num/den;
		System.out.println(num + " / " +den);
		System.out.println("Resultat : " + sim);
		System.out.println(sim + " " + num + " " + den);
		
		//Calcul de l'angle
		sim = (float) Math.acos(sim);
		
		return sim;
	}
	

	/**
	 * Effectue une recherche de la requete suivant le modele du booleen etendu
	 * @param requete La chaine passée en parametre par l'utilisateur
	 * @return Liste de document triés suivant la pertinence
	 */
	public List<Entry<String, Document>> rechercheBooleenEtendu(String requete){
		requete = requete.toLowerCase();
		
		//On crŽe les objets reprŽsentants la requete
		Operation op = Factory.make(requete);
		//On lance les calculs
		HashMap<String, Document> docPertinents = op.compute(bibli);	
		//On tri et selectionne les ŽlŽments pertinents
		List<Entry<String, Document>> renvoi = op.elaguer(bibli, docPertinents);
		
		//RAZ des documents pertinents
		docPertinents = new HashMap<String, Document>();
		return renvoi;
	}

}

